﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal abstract class IndexDataSourceProviderHelper
  {
    #region Static Fields

    private static readonly Guid EmptyCallId = DataUtils.CreateCallId();
    private static readonly Guid GetDataCallId = DataUtils.CreateCallId();
    private static readonly Guid GetFixedGroupCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupCountsCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupCountGroupStartIndexCallId = DataUtils.CreateCallId();
    private static readonly Guid GetGroupSizeCallId = DataUtils.CreateCallId();
    private static readonly Guid GetItemsCallId = DataUtils.CreateCallId();
    private static readonly Guid GetItemsCountCallId = DataUtils.CreateCallId();
    private static readonly Guid GetSizeCallId = DataUtils.CreateCallId();
    private static readonly Guid GetStartIndexCallId = DataUtils.CreateCallId();
    private static readonly Guid GetStatValueCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    internal IndexDataSourceProviderHelper( IEnumerable<SortDescription> defaultSortOrder )
    {
      m_defaultSortOrder = ( defaultSortOrder != null )
                             ? defaultSortOrder.ToList()
                             : new List<SortDescription>( 0 );

      m_emptyDependentCall = new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.EmptyCallId, cb, st );
          asyncResult.StartSync( null );

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.EmptyCallId );

          return new DependentCallResult( iar.GetCallResult<object>() );
        } );
    }

    #endregion

    #region CanFind Protected Property

    protected virtual bool CanFind
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region DefaultSortOrder Private Property

    private ICollection<SortDescription> DefaultSortOrder
    {
      get
      {
        return m_defaultSortOrder;
      }
    }

    private ICollection<SortDescription> m_defaultSortOrder;

    #endregion

    #region EmptyDependentCall Private Property

    private DependentCall EmptyDependentCall
    {
      get
      {
        return m_emptyDependentCall;
      }
    }

    private DependentCall m_emptyDependentCall;

    #endregion

    protected abstract bool CanSortBy( IEnumerable<SortDescription> sortOrder );

    protected abstract IAsyncResult BeginCountQuery(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );
    protected abstract CountQueryResult EndCountQuery( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginDataQuery(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state );
    protected abstract DataQueryResult EndDataQuery( IAsyncResult asyncResult );

    protected virtual IAsyncResult BeginFindQuery(
      FindQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected virtual FindQueryResult EndFindQuery( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected abstract IAsyncResult BeginGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state );

    protected abstract GroupCountQueryResult EndGroupCountQuery( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state );
    protected abstract StatQueryResult EndStatQuery( IAsyncResult asyncResult );

    internal IAsyncResult BeginGetItemsCount(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      QueryContext context = new QueryContext();
      context.IsQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      CountQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );

      SortRequest sortRequest = newQueryParameters.Sort;
      FilterRequest filterRequest = newQueryParameters.Filter;
      FilterGroupRequest filterGroupRequest = newQueryParameters.FilterGroup;
      JumpRequest jumpRequest = newQueryParameters.Jump;

      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetItemsCountCallId, callback, state );

      // Retrieve the data source size only if necessary.
      if( ( context.IsQueryingForward ) || ( jumpRequest == null ) )
      {
        asyncResult.AddDependentCall(
          this.GetDataSourceSize(
            sortRequest,
            filterRequest,
            filterGroupRequest,
            context ) );
      }

      // Retrieve the index of the target if necessary.
      if( jumpRequest != null )
      {
        Debug.Assert( ( filterGroupRequest == null ) && ( this.CanFind ) );

        asyncResult.AddDependentCall(
          this.FindGroupStartIndex( jumpRequest, sortRequest, filterRequest, context ) );
      }

      // Calculate the items count from the information of the previous queries.
      asyncResult.AddDependentCall( IndexDataSourceProviderHelper.GetItemsCount( context ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal CountQueryResult EndGetItemsCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetItemsCountCallId );

      return asyncResult.GetCallResult<CountQueryResult>();
    }

    internal IAsyncResult BeginGetItems(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      QueryContext context = new QueryContext();
      context.IsQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      DataQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );
      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetItemsCallId, callback, state );

      SortRequest sortRequest = newQueryParameters.Sort;
      FilterRequest filterRequest = newQueryParameters.Filter;
      ItemJumpRequest itemJumpRequest = newQueryParameters.ItemJump;
      JumpRequest jumpRequest = newQueryParameters.Jump;
      SkipRequest skipRequest = newQueryParameters.Skip;
      TakeRequest takeRequest = newQueryParameters.Take;

      // Retrieve the starting index and set it in the context.
      if( itemJumpRequest != null )
      {
        asyncResult.AddDependentCall(
          this.GetItemStartIndex(
            itemJumpRequest,
            sortRequest,
            filterRequest,
            context ) );
      }
      else if( jumpRequest != null )
      {
        asyncResult.AddDependentCall(
          this.FindGroupStartIndex(
            jumpRequest,
            sortRequest,
            filterRequest,
            context ) );
      }
      else
      {
        asyncResult.AddDependentCall(
          this.GetStartIndex(
            sortRequest,
            filterRequest,
            context ) );
      }

      // Retrieve the requested items.
      asyncResult.AddDependentCall( this.GetData( newQueryParameters, context ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal DataQueryResult EndGetItems( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetItemsCallId );

      return asyncResult.GetCallResult<DataQueryResult>();
    }

    internal IAsyncResult BeginGetGroupCount(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      GroupCountQueryContext context = new GroupCountQueryContext();
      context.IsQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      GroupCountQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );
      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetGroupCountCallId, callback, state );

      SortRequest sortRequest = newQueryParameters.Sort;
      FilterRequest filterRequest = newQueryParameters.Filter;
      FilterGroupRequest filterGroupRequest = newQueryParameters.FilterGroup;
      JumpRequest jumpRequest = newQueryParameters.Jump;

      // Retrieve the data source size if necessary.
      if( ( context.IsQueryingForward ) || ( jumpRequest == null ) )
      {
        asyncResult.AddDependentCall(
          this.GetDataSourceSize(
            sortRequest,
            filterRequest,
            null,
            context ) );
      }

      // Retrieve the group size and it's start index.
      if( filterGroupRequest != null )
      {
        asyncResult.AddDependentCall(
          this.GetGroupSize(
            sortRequest,
            filterRequest,
            filterGroupRequest,
            context ) );

        asyncResult.AddDependentCall(
          this.FindGroupCountGroupStartIndex(
            filterGroupRequest,
            sortRequest,
            filterRequest,
            context ) );
      }

      // Retrieve the starting index.
      if( jumpRequest != null )
      {
        asyncResult.AddDependentCall(
          this.FindGroupStartIndex(
            jumpRequest,
            sortRequest,
            filterRequest,
            context ) );
      }
      else
      {
        asyncResult.AddDependentCall(
          this.GetStartIndex(
            sortRequest,
            filterRequest,
            context ) );
      }

      // Retrieve the group count.
      asyncResult.AddDependentCall(
        this.GetGroupCount(
          sortRequest,
          filterRequest,
          context ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal GroupCountQueryResult EndGetGroupCount( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetGroupCountCallId );

      return asyncResult.GetCallResult<GroupCountQueryResult>();
    }

    internal IAsyncResult BeginGetStatValue(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      StatQueryParameters newQueryParameters = this.ConvertQueryParameters( queryParameters );

      AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStatValueCallId, callback, state );

      // Retrieve the requested items.
      asyncResult.AddDependentCall(
        ( cb, st ) => this.BeginStatQuery( newQueryParameters, cb, st ),
        ( par, iar ) => new DependentCallResult( this.EndStatQuery( iar ) ) );

      asyncResult.Start();

      return asyncResult;
    }

    internal StatQueryResult EndGetStatValue( IAsyncResult asyncResult )
    {
      asyncResult.ValidateCallEnd( IndexDataSourceProviderHelper.GetStatValueCallId );

      return asyncResult.GetCallResult<StatQueryResult>();
    }

    private DependentCall GetData( DataQueryParameters queryParameters, QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          // Find out the number of items to skip.
          long skipCount = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;

          // Since the targeted starting item was deleted, we will start next to it.
          // We need to adjust the skip counter accordingly.
          if( ( context.TargetDeleted ) && ( skipCount > 0 ) )
          {
            skipCount--;
          }

          // Find out the number of items to take.
          Nullable<int> takeCount = null;
          if( queryParameters.Take != null )
          {
            takeCount = queryParameters.Take.Count;
          }

          // Find out the starting index and the number of items to take.
          long targetIndex = context.StartIndex;
          long startIndex;
          Nullable<int> length;

          IndexDataSourceProviderHelper.GetIndexRange(
            targetIndex,
            skipCount,
            takeCount,
            context.IsQueryingForward,
            out startIndex,
            out length );

          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetDataCallId, cb, st );

          // There is no item to get if we're starting out of bound or simply
          // if no item must be taken.
          if( ( startIndex < 0 ) || ( length == 0 ) )
          {
            asyncResult.AddDependentCall( this.GetDataOutOfBound( context ) );
          }
          else
          {
            SortRequest sortRequest = queryParameters.Sort;
            FilterRequest filterRequest = queryParameters.Filter;

            // Retrieve the data source size before the items.
            asyncResult.AddDependentCall(
              this.GetDataSourceSize(
                sortRequest,
                filterRequest,
                null,
                context ) );

            // Retrieve the targeted items.
            asyncResult.AddDependentCall(
              this.GetData(
                sortRequest,
                filterRequest,
                targetIndex,
                startIndex,
                length,
                context ) );
          }

          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetDataCallId );

          return new DependentCallResult( iar.GetCallResult<DataQueryResult>() );
        } );
    }

    private DependentCall GetData(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      long targetIndex,
      long startIndex,
      Nullable<int> length,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          SkipRequest skipRequest = ( startIndex > 0 ) ? new SkipRequest( startIndex ) : null;
          TakeRequest takeRequest = ( length.HasValue ) ? new TakeRequest( length.Value ) : null;

          // Create the query parameters to be use by the DataSourceProvider.
          DataQueryParameters newQueryParameters = new DataQueryParameters(
                                                     takeRequest,
                                                     skipRequest,
                                                     sortRequest,
                                                     filterRequest,
                                                     null,
                                                     null );

          return this.BeginDataQuery( newQueryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          DataQueryResult queryResult = this.EndDataQuery( iar );

          // Find out the total number of items in the source.
          long totalCount = queryResult.TotalCount.GetValueOrDefault( context.DataSourceSize.Value );

          // Retrieve the context for which the index will be generated.
          DataQueryContext keyContext = IndexDataSourceProviderHelper.GetCurrentContext(
                                          sortRequest,
                                          filterRequest,
                                          totalCount );

          // Retrieve the items found.
          List<IndexDataObject> dataObjects;
          if( queryResult.Data != null )
          {
            dataObjects = queryResult.Data.Cast<object>().Select(
                            ( item, offset ) => new IndexDataObject( item, startIndex + offset, keyContext ) ).ToList();
          }
          else
          {
            dataObjects = new List<IndexDataObject>( 0 );
          }

          bool isQueryingForward = context.IsQueryingForward;

          // The result must be reversed if we were querying in the opposite direction.
          if( !isQueryingForward )
          {
            dataObjects.Reverse();
          }

          // If we would have queried backward, we would have started after the
          // end of the source.
          if( ( !isQueryingForward ) && ( dataObjects.Count != length ) )
            return new DependentCallResult(
              new DataQueryResult(
                Enumerable.Empty<IndexDataObject>(),
                null,
                totalCount ) );

          Nullable<long> inlineCount = null;

          if( isQueryingForward )
          {
            // The inline count given by the external data source cannot be used explicitly
            // because the current helper may have calculated the starting index from a
            // request that does or doesn't influence the inline count.
            if( queryResult.InlineCount.HasValue )
            {
              inlineCount = queryResult.InlineCount.Value - targetIndex;
            }
            // We'll try to calculate the inline count from the total count we already have.
            else
            {
              inlineCount = totalCount - targetIndex;

              // Since our total count may no be up to date anymore, we must not return an
              // invalid inline count in the current context.
              if( ( inlineCount < 0 ) || ( inlineCount.Value + dataObjects.Count > totalCount ) )
              {
                inlineCount = null;
              }
            }
          }
          else
          {
            inlineCount = targetIndex + 1;
          }

          return new DependentCallResult( new DataQueryResult( dataObjects, inlineCount, totalCount ) );
        } );
    }

    private DependentCall GetDataOutOfBound( QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          Nullable<long> inlineCount = null;
          Nullable<long> totalCount = context.DataSourceSize;

          if( context.IsQueryingForward )
          {
            if( totalCount.HasValue )
            {
              inlineCount = Math.Max( totalCount.Value - context.StartIndex, 0 );
            }
          }
          else
          {
            inlineCount = context.StartIndex + 1;
          }

          IEnumerable<IndexDataObject> dataObjects = Enumerable.Empty<IndexDataObject>();

          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetDataCallId, cb, st );

          asyncResult.StartSync(
            new DataQueryResult(
              Enumerable.Empty<IndexDataObject>(),
              inlineCount,
              totalCount ) );

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetDataCallId );

          return new DependentCallResult( iar.GetCallResult<DataQueryResult>() );
        } );
    }

    private DependentCall GetGroupCount(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      GroupCountQueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetGroupCountsCallId, cb, st );

          long index;
          long length;

          // The previous queries we able to locate the targeted items.
          if( context.GroupExists )
          {
            bool forward = context.IsQueryingForward;

            // A FilterGroupRequest was targeting a group.
            if( context.GroupSize.HasValue )
            {
              long groupSize = context.GroupSize.Value;
              long startIndex = context.StartIndex;
              long groupStartIndex = context.GroupStartIndex;

              if( forward )
              {
                index = Math.Max( startIndex, groupStartIndex );
                length = Math.Max( 0, groupSize - ( index - groupStartIndex ) );
              }
              else
              {
                index = Math.Min( startIndex, groupStartIndex );
                length = Math.Max( 0, startIndex - groupStartIndex + 1 );
              }
            }
            // There was no FilterGroupRequest.
            else
            {
              if( forward )
              {
                index = context.StartIndex;
                length = Math.Max( 0, context.DataSourceSize.Value - index );
              }
              else
              {
                index = 0;
                length = context.StartIndex + 1;
              }
            }
          }
          else
          {
            index = 0;
            length = 0;
          }

          if( ( length <= 0 ) || ( index < 0 ) )
          {
            asyncResult.AddDependentCall( this.GetEmptyGroupCount( sortRequest ) );
          }
          else if( length == 1 )
          {
            asyncResult.AddDependentCall( this.GetUniqueGroupCount( sortRequest ) );
          }
          else
          {
            SkipRequest skipRequest = ( index > 0 ) ? new SkipRequest( index ) : null;
            TakeRequest takeRequest = new TakeRequest( Convert.ToInt32( length ) );

            asyncResult.AddDependentCall(
              this.GetGroupCount(
                sortRequest,
                filterRequest,
                skipRequest,
                takeRequest ) );
          }

          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetGroupCountsCallId );

          return new DependentCallResult( iar.GetCallResult<GroupCountQueryResult>() );
        } );
    }

    private DependentCall GetGroupCount(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      SkipRequest skipRequest,
      TakeRequest takeRequest )
    {
      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      if( takeRequest == null )
        throw new ArgumentNullException( "takeRequest" );

      return new DependentCall(
        ( cb, st ) =>
        {
          SkipRequest startIndex = ( ( skipRequest != null ) && ( skipRequest.Count > 0 ) )
                                     ? skipRequest
                                     : null;

          GroupCountQueryParameters queryParameters = new GroupCountQueryParameters(
                                                        sortRequest,
                                                        filterRequest,
                                                        null,
                                                        null,
                                                        startIndex,
                                                        takeRequest );

          return this.BeginGroupCountQuery( queryParameters, cb, st );
        },
        ( par, iar ) => new DependentCallResult( this.EndGroupCountQuery( iar ) ) );
    }

    private DependentCall GetEmptyGroupCount( SortRequest sortRequest )
    {
      return this.GetFixedGroupCount( sortRequest, 0 );
    }

    private DependentCall GetUniqueGroupCount( SortRequest sortRequest )
    {
      return this.GetFixedGroupCount( sortRequest, 1 );
    }

    private DependentCall GetFixedGroupCount( SortRequest sortRequest, long count )
    {
      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      return new DependentCall(
        ( cb, st ) =>
        {
          IEnumerable<long> groupCount = Enumerable.Repeat( count, sortRequest.SortDescriptions.Count ).ToList();
          GroupCountQueryResult queryResult = new GroupCountQueryResult( groupCount );

          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetFixedGroupCountCallId, cb, st );
          asyncResult.StartSync( queryResult );

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetFixedGroupCountCallId );

          return new DependentCallResult( iar.GetCallResult<GroupCountQueryResult>() );
        } );
    }

    private DependentCall GetItemStartIndex(
      ItemJumpRequest jumpRequest,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      QueryContext context )
    {
      if( jumpRequest == null )
        throw new ArgumentNullException( "jumpRequest" );

      if( context == null )
        throw new ArgumentNullException( "context" );

      IndexDataItem targetItem = jumpRequest.Item as IndexDataItem;
      if( targetItem == null )
        throw new DataInternalException();

      return this.GetItemStartIndex( sortRequest, filterRequest, targetItem.Index, targetItem.IsDeleted, context );
    }

    private DependentCall GetItemStartIndex(
      JumpRequest jumpRequest,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      QueryContext context )
    {
      if( jumpRequest == null )
        throw new ArgumentNullException( "jumpRequest" );

      if( context == null )
        throw new ArgumentNullException( "context" );

      IndexKey key = ( from value in jumpRequest.Values
                       let k = value.Value as IndexKey
                       where k != null
                       select k ).FirstOrDefault();

      if( key != null )
        return this.GetItemStartIndex( sortRequest, filterRequest, key, false, context );

      return null;
    }

    private DependentCall GetItemStartIndex(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      IndexKey key,
      bool targetItemDeleted,
      QueryContext context )
    {
      // Make sure the index is specified.
      bool reverseOrder = IndexDataSourceProviderHelper.ValidateIndexKey( key, sortRequest, filterRequest );
      long startIndex = key.Value.Value;

      context.StartIndex = startIndex;
      context.TargetDeleted = targetItemDeleted;

      return this.GetItemStartIndex( filterRequest, context, reverseOrder );
    }

    private DependentCall GetItemStartIndex(
      FilterRequest filterRequest,
      QueryContext context,
      bool isStartIndexReversed )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      // The StartIndex is good as it is.
      if( !isStartIndexReversed )
        return this.EmptyDependentCall;

      // Create the DependentCall that calculates the index based on the data source size.
      DependentCall calculateIndex =
        new DependentCall(
          ( cb, st ) =>
          {
            AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStartIndexCallId, cb, st );

            long startIndex = Math.Max( 0, context.DataSourceSize.Value - context.StartIndex - 1 );
            context.StartIndex = startIndex;

            asyncResult.StartSync( startIndex );

            return asyncResult;
          },
          ( par, iar ) =>
          {
            iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetStartIndexCallId );

            return new DependentCallResult( null );
          } );

      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStartIndexCallId, cb, st );

          asyncResult.AddDependentCall( this.GetDataSourceSize( null, filterRequest, null, context ) );
          asyncResult.AddDependentCall( calculateIndex );
          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetStartIndexCallId );

          return new DependentCallResult( null );
        } );
    }

    private DependentCall GetStartIndex(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      context.TargetDeleted = false;

      // Start at index 0.
      if( context.IsQueryingForward )
      {
        context.StartIndex = 0;

        return this.EmptyDependentCall;
      }

      // Create the DependentCall that will return a StartIndex of data source size - 1.
      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetStartIndexCallId, cb, st );

          asyncResult.AddDependentCall( this.GetDataSourceSize( sortRequest, filterRequest, null, context ) );
          asyncResult.Start();

          return asyncResult;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetStartIndexCallId );

          context.StartIndex = context.DataSourceSize.Value - 1;

          return new DependentCallResult( null );
        } );
    }

    private DependentCall FindGroupStartIndex(
      JumpRequest jumpRequest,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( jumpRequest == null )
        throw new ArgumentNullException( "jumpRequest" );

      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      DependentCall indexJump = this.GetItemStartIndex( jumpRequest, sortRequest, filterRequest, context );
      if( indexJump != null )
        return indexJump;

      if( !this.CanFind )
        throw new DataInternalException();

      return new DependentCall(
        ( cb, st ) =>
        {
          // Convert the JumpRequest into a FindRequest.
          IEnumerable<FindValue> findValues = ( from jumpValue in jumpRequest.Values.Take( sortRequest.SortDescriptions.Count )
                                                select new FindValue( jumpValue.Value ) );
          FindPosition findPosition = ( jumpRequest.StartPosition == JumpStartPosition.Before )
                                        ? FindPosition.First
                                        : FindPosition.Last;
          FindRequest findRequest = new FindRequest( findValues, findPosition );

          FindQueryParameters queryParameters = new FindQueryParameters( sortRequest, filterRequest, findRequest );

          return this.BeginFindQuery( queryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          FindQueryResult queryResult = this.EndFindQuery( iar );
          FindResult result = queryResult.Result;
          long index = result.Index;

          // Adjust the index if necessary since the find operation returned the index of the target
          // position while the jump ask for the position before or after a given target.
          if( result.IsMatch )
          {
            if( jumpRequest.StartPosition == JumpStartPosition.After )
            {
              if( context.IsQueryingForward )
              {
                index++;
              }
              else
              {
                index--;
              }
            }
          }

          context.StartIndex = index;
          context.TargetDeleted = !result.IsMatch;

          return new DependentCallResult( index );
        } );
    }

    private DependentCall FindGroupCountGroupStartIndex(
      FilterGroupRequest filterGroupRequest,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      GroupCountQueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      if( filterGroupRequest == null )
        throw new ArgumentNullException( "filterGroupRequest" );

      if( sortRequest == null )
        throw new ArgumentNullException( "sortRequest" );

      if( !this.CanFind )
        throw new DataInternalException();

      return new DependentCall(
        ( cb, st ) =>
        {
          // The target group doesn't exist.  There is no need to start a query to find it.
          if( !context.GroupExists )
          {
            AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetGroupCountGroupStartIndexCallId, cb, st );
            asyncResult.AddDependentCall( this.EmptyDependentCall );
            asyncResult.Start();

            return asyncResult;
          }

          // Create a query to find the start index of the target group.
          IEnumerable<FindValue> findValues = ( from filterValue in filterGroupRequest.Values.Take( sortRequest.SortDescriptions.Count )
                                                select new FindValue( filterValue.Value ) );
          FindRequest findRequest = new FindRequest( findValues, FindPosition.First );
          FindQueryParameters queryParameters = new FindQueryParameters( sortRequest, filterRequest, findRequest );

          return this.BeginFindQuery( queryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          if( !context.GroupExists )
          {
            iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetGroupCountGroupStartIndexCallId );
          }
          else
          {
            FindQueryResult queryResult = this.EndFindQuery( iar );
            FindResult result = queryResult.Result;

            context.GroupExists = result.IsMatch;
            context.GroupStartIndex = result.Index;
          }

          return new DependentCallResult( null );
        } );
    }

    private DependentCall GetDataSourceSize(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      FilterGroupRequest filterGroupRequest,
      QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Func<Nullable<long>> sizeGetter = () => context.DataSourceSize;
      Action<long> sizeSetter = ( size ) => context.DataSourceSize = size;

      return this.GetSize(
        sortRequest,
        filterRequest,
        filterGroupRequest,
        sizeGetter,
        sizeSetter );
    }

    private DependentCall GetGroupSize(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      FilterGroupRequest filterGroupRequest,
      GroupCountQueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      Func<Nullable<long>> sizeGetter = () => context.GroupSize;
      Action<long> sizeSetter =
        ( size ) =>
        {
          context.GroupSize = size;
          context.GroupExists = ( size > 0 );
        };

      return this.GetSize(
        sortRequest,
        filterRequest,
        filterGroupRequest,
        sizeGetter,
        sizeSetter );
    }

    private DependentCall GetSize(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      FilterGroupRequest filterGroupRequest,
      Func<Nullable<long>> sizeGetter,
      Action<long> sizeSetter )
    {
      if( sizeGetter == null )
        throw new ArgumentNullException( "sizeGetter" );

      if( sizeSetter == null )
        throw new ArgumentNullException( "sizeSetter" );

      Nullable<long> currentSize = sizeGetter.Invoke();

      // We already have the size.
      if( currentSize.HasValue )
        return new DependentCall(
          ( cb, st ) =>
          {
            AsyncResult asyncResult = new AsyncResult( IndexDataSourceProviderHelper.GetSizeCallId, cb, st );
            asyncResult.StartSync( currentSize.Value );

            return asyncResult;
          },
          ( par, iar ) =>
          {
            iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetSizeCallId );

            return new DependentCallResult( null );
          } );

      // Find out the target size.
      return new DependentCall(
        ( cb, st ) =>
        {
          CountQueryParameters queryParameters;

          if( filterGroupRequest == null )
          {
            queryParameters = new CountQueryParameters( null, filterRequest, null, null );
          }
          else
          {
            queryParameters = new CountQueryParameters( sortRequest, filterRequest, null, filterGroupRequest );
          }

          return this.BeginCountQuery( queryParameters, cb, st );
        },
        ( par, iar ) =>
        {
          CountQueryResult queryResult = this.EndCountQuery( iar );
          long count = queryResult.Count;

          sizeSetter.Invoke( count );

          return new DependentCallResult( null );
        } );
    }

    private CountQueryParameters ConvertQueryParameters( CountQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new CountQueryParameters(
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        this.ConvertToDefaultJumpRequest( queryParameters.Jump, isQueryingForward ),
        queryParameters.FilterGroup );
    }

    private DataQueryParameters ConvertQueryParameters( DataQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new DataQueryParameters(
        queryParameters.Take,
        queryParameters.Skip,
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        queryParameters.ItemJump,
        this.ConvertToDefaultJumpRequest( queryParameters.Jump, isQueryingForward ) );
    }

    private GroupCountQueryParameters ConvertQueryParameters( GroupCountQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      Debug.Assert( ( queryParameters.Skip == null )
                 && ( queryParameters.Take == null ) );

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new GroupCountQueryParameters(
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        this.ConvertToDefaultJumpRequest( queryParameters.Jump, isQueryingForward ),
        queryParameters.FilterGroup,
        null,
        null );
    }

    private StatQueryParameters ConvertQueryParameters( StatQueryParameters queryParameters )
    {
      // Make sure the filter isn't referencing the IndexDataItem.IndexKeyName field.
      if( IndexDataSourceProviderHelper.IsUsingIndexKey( queryParameters.Filter ) )
        // The filter references the index field, which isn't supported.
        throw new DataInternalException();

      bool isQueryingForward = this.IsQueryingForward( queryParameters.Sort );

      return new StatQueryParameters(
        queryParameters.Function,
        this.ConvertToDefaultSortRequest( queryParameters.Sort, isQueryingForward ),
        queryParameters.Filter,
        queryParameters.FilterGroup );
    }

    private SortRequest ConvertToDefaultSortRequest( SortRequest sortRequest, bool isQueryingForward )
    {
      List<SortDescription> newSortOrder = new List<SortDescription>();

      if( sortRequest != null )
      {
        foreach( SortDescription sortOrder in sortRequest.SortDescriptions )
        {
          if( sortOrder.PropertyName == IndexDataItem.IndexKeyName )
          {
            newSortOrder.AddRange( m_defaultSortOrder );
          }
          else if( isQueryingForward )
          {
            newSortOrder.Add( sortOrder );
          }
          else
          {
            newSortOrder.Add( new SortDescription(
                                sortOrder.PropertyName,
                                ( sortOrder.Direction == ListSortDirection.Ascending )
                                  ? ListSortDirection.Descending
                                  : ListSortDirection.Ascending ) );
          }
        }
      }

      if( newSortOrder.Count <= 0 )
        return null;

      return new SortRequest( newSortOrder );
    }

    private JumpRequest ConvertToDefaultJumpRequest( JumpRequest jumpRequest, bool isQueryingForward )
    {
      if( ( jumpRequest == null ) || ( isQueryingForward ) )
        return jumpRequest;

      JumpStartPosition startPosition = ( jumpRequest.StartPosition == JumpStartPosition.After )
                                          ? JumpStartPosition.Before
                                          : JumpStartPosition.After;

      return new JumpRequest( jumpRequest.Values, startPosition );
    }

    private bool IsQueryingForward( SortRequest sortRequest )
    {
      if( ( sortRequest == null ) || ( sortRequest.SortDescriptions.Count <= 0 ) )
        return true;

      IEnumerable<SortDescription> sortBy = sortRequest.SortDescriptions;

      foreach( SortDescription sortOrder in sortBy )
      {
        if( sortOrder.PropertyName == IndexDataItem.IndexKeyName )
          return ( sortOrder.Direction == ListSortDirection.Ascending );
      }

      // If the sort isn't using the index, we'll use it as is.
      if( this.CanSortBy( sortBy ) )
        return true;

      IEnumerable<SortDescription> reversedOrder = ( from sort in sortBy
                                                     let direction = ( sort.Direction == ListSortDirection.Ascending )
                                                                       ? ListSortDirection.Descending
                                                                       : ListSortDirection.Ascending
                                                     select new SortDescription( sort.PropertyName, direction ) ).ToList();

      // If the sort is usable in reversed order, use it.
      if( this.CanSortBy( reversedOrder ) )
        return false;

      throw new DataInternalException();
    }

    private static DependentCall GetItemsCount( QueryContext context )
    {
      if( context == null )
        throw new ArgumentNullException( "context" );

      return new DependentCall(
        ( cb, st ) =>
        {
          AsyncResult ar = new AsyncResult( IndexDataSourceProviderHelper.GetItemsCountCallId, cb, st );

          // The StartIndex is set to 0 if the JumpRequest is null.
          long startIndex = context.StartIndex;
          long itemsCount;

          if( context.IsQueryingForward )
          {
            long dataSourceSize = context.DataSourceSize.Value;

            itemsCount = Math.Max( 0, dataSourceSize - startIndex );
          }
          else
          {
            itemsCount = Math.Max( 0, startIndex + 1 );
          }

          ar.StartSync( new CountQueryResult( itemsCount ) );

          return ar;
        },
        ( par, iar ) =>
        {
          iar.ValidateCallEnd( IndexDataSourceProviderHelper.GetItemsCountCallId );

          return new DependentCallResult( iar.GetCallResult<CountQueryResult>() );
        } );
    }

    private static void GetIndexRange(
      long targetIndex,
      long skipCount,
      Nullable<int> takeCount,
      bool forward,
      out long startIndex,
      out Nullable<int> length )
    {
      startIndex = targetIndex + ( ( forward ) ? skipCount : -skipCount );
      length = takeCount;

      // Since the target index if before the beginning of the source, there
      // is no item to get.
      if( startIndex < 0 )
      {
        length = 0;
      }

      if( forward || ( length == 0 ) || ( startIndex < 0 ) )
        return;

      long endIndex = startIndex;

      if( takeCount.HasValue )
      {
        startIndex = Math.Max( startIndex - takeCount.Value + 1, 0 );
      }
      else
      {
        startIndex = 0;
      }

      length = Convert.ToInt32( endIndex - startIndex + 1 );
    }

    private static DataQueryContext GetCurrentContext(
      SortRequest sortRequest,
      FilterRequest filterRequest,
      long dataSourceSize )
    {
      IEnumerable<SortDescription> sortOrder = ( sortRequest != null )
                                                 ? sortRequest.SortDescriptions
                                                 : Enumerable.Empty<SortDescription>();
      Predicate<object> filterPredicate = null;
      FilterExpressionBase filterExpression = null;

      if( filterRequest != null )
      {
        filterPredicate = filterRequest.Filter;
        filterExpression = filterRequest.Expression;
      }

      return new DataQueryContext( sortOrder, filterPredicate, filterExpression, dataSourceSize );
    }

    private static bool ValidateIndexKey(
      IndexKey key,
      SortRequest sortRequest,
      FilterRequest filterRequest )
    {
      bool reverse;

      if( !IndexDataSourceProviderHelper.IsIndexKeyValid( key, sortRequest, filterRequest, out reverse ) )
        throw new DataInternalException();

      return reverse;
    }

    private static bool IsIndexKeyValid(
      IndexKey key,
      SortRequest sortRequest,
      FilterRequest filterRequest,
      out bool reverse )
    {
      reverse = false;

      if( ( key == null )
        || ( key.Context == null )
        || ( !key.Value.HasValue ) )
        return false;

      // Make sure the key is use in the same context except for the sorting order.
      DataQueryContext context = key.Context;
      bool isUsableContext = ( context != DataQueryContext.None );

      // Make sure the key has been created in the same filtering context.
      isUsableContext &= ( ( filterRequest == null ) && ( context.FilterPredicate == null ) && ( context.FilterExpression == null ) )
                      || ( ( filterRequest != null ) && ( context.FilterPredicate == filterRequest.Filter ) && ( context.FilterExpression == filterRequest.Expression ) );

      // Make sure the key has been created in the same sorting context.
      // A reverse sort order is considered valid because it has no impact on the
      // index value.
      isUsableContext &= ( ( sortRequest == null ) && ( context.SortDescriptions.Count <= 0 ) )
                      || ( ( sortRequest != null ) && ( context.SortDescriptions.Count == sortRequest.SortDescriptions.Count ) );

      if( ( isUsableContext ) && ( sortRequest != null ) )
      {
        int fieldsCount = sortRequest.SortDescriptions.Count;
        Nullable<bool> reverseOrder = null;

        // Make sure the the index key sorting context and the current sorting context
        // share the same sorting fields and are either in the same order or in
        // reverse order.
        for( int i = 0; ( isUsableContext ) && ( i < fieldsCount ); i++ )
        {
          if( sortRequest.SortDescriptions[ i ].PropertyName == context.SortDescriptions[ i ].PropertyName )
          {
            bool isReversed = ( sortRequest.SortDescriptions[ i ].Direction != context.SortDescriptions[ i ].Direction );
            if( reverseOrder.HasValue )
            {
              isUsableContext = ( reverseOrder.Value == isReversed );
            }
            else
            {
              reverseOrder = isReversed;
            }
          }
          else
          {
            isUsableContext = false;
          }
        }

        if( isUsableContext )
        {
          reverse = reverseOrder.GetValueOrDefault( false );
        }
      }

      return isUsableContext;
    }

    private static bool IsUsingIndexKey( FilterRequest filterRequest )
    {
      if( ( filterRequest == null ) || ( filterRequest.Expression == null ) )
        return false;

      IndexKeyUsageVisitor verifier = new IndexKeyUsageVisitor( filterRequest.Expression );

      return verifier.Result;
    }

    #region Private QueryContext Nested Type

    private class QueryContext
    {
      internal QueryContext()
      {
        this.IsQueryingForward = true;
      }

      internal Nullable<long> DataSourceSize
      {
        get;
        set;
      }

      internal long StartIndex
      {
        get;
        set;
      }

      internal bool TargetDeleted
      {
        get;
        set;
      }

      internal bool IsQueryingForward
      {
        get;
        set;
      }
    }

    #endregion

    #region Private GroupCountQueryContext Nested Type

    private class GroupCountQueryContext : QueryContext
    {
      internal GroupCountQueryContext()
      {
        this.GroupExists = true;
      }

      internal Nullable<long> GroupSize
      {
        get;
        set;
      }

      internal long GroupStartIndex
      {
        get;
        set;
      }

      internal bool GroupExists
      {
        get;
        set;
      }
    }

    #endregion

    #region Private IndexKeyUsageVisitor Nested Type

    private class IndexKeyUsageVisitor : FilterExpressionVisitor
    {
      internal IndexKeyUsageVisitor( FilterExpressionBase expression )
        : base( expression )
      {
      }

      internal bool Result
      {
        get
        {
          if( !m_hasRun )
          {
            this.Visit();
            m_hasRun = true;
          }

          return m_result;
        }
      }

      protected override void Initialize()
      {
        m_result = false;

        base.Initialize();
      }

      protected internal override void Visit( FilterExpression expression )
      {
        m_result |= ( expression.MemberName == IndexDataItem.IndexKeyName );
      }

      protected internal override void Visit( AndFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( OrFilterExpression expression )
      {
        this.Visit( ( FilterExpressionOperator )expression );
      }

      protected internal override void Visit( NotFilterExpression expression )
      {
        if( !m_result )
        {
          this.Visit( expression.ChildExpression );
        }
      }

      private void Visit( FilterExpressionOperator expression )
      {
        if( !m_result )
        {
          foreach( FilterExpressionBase childExpression in expression.ChildExpressions )
          {
            if( m_result )
              return;

            this.Visit( childExpression );
          }
        }
      }

      private bool m_result;
      private bool m_hasRun;
    }

    #endregion
  }
}
